const webpack = require('webpack'),
    spawn = require('cross-spawn'),
    path = require('path'),
    tmp = require('tmp'),
    formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'),
    fs = require('fs-extra'),
    loadJsonFile = require('load-json-file'),
    hash = require('object-hash');

const getTemporaryDirectory = () => {
    return new Promise((resolve, reject) => {
        // Unsafe cleanup lets us recursively delete the directory if it contains
        // contents; by default it only allows removal if it's empty
        tmp.dir({unsafeCleanup: true}, (err, tmpdir, callback) => {
            if (err) {
                reject(err);
            } else {
                resolve({
                    tmpdir: tmpdir,
                    cleanup: () => {
                        try {
                            callback();
                        } catch (ignored) {
                            // Callback might throw and fail, since it's a temp directory the
                            // OS will clean it up eventually...
                        }
                    }
                });
            }
        });
    });
};

const createEntry = (list) => {
    return `
    require('es6-promise/auto');
    require('react-app-polyfill/ie9');
    //location.origin fixed ie10
    if(!window.location.origin){
        window.location.origin = window.location.protocol+'//'+window.location.hostname;
    }
    // https://tc39.github.io/ecma262/#sec-array.prototype.find
if (!Array.prototype.find) {
  Object.defineProperty(Array.prototype, 'find', {
    value: function(predicate) {
      // 1. Let O be ? ToObject(this value).
      if (this == null) {
        throw TypeError('"this" is null or not defined');
      }

      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If IsCallable(predicate) is false, throw a TypeError exception.
      if (typeof predicate !== 'function') {
        throw TypeError('predicate must be a function');
      }

      // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
      var thisArg = arguments[1];

      // 5. Let k be 0.
      var k = 0;

      // 6. Repeat, while k < len
      while (k < len) {
        // a. Let Pk be ! ToString(k).
        // b. Let kValue be ? Get(O, Pk).
        // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
        // d. If testResult is true, return kValue.
        var kValue = o[k];
        if (predicate.call(thisArg, kValue, k, o)) {
          return kValue;
        }
        // e. Increase k by 1.
        k++;
      }

      // 7. Return undefined.
      return undefined;
    },
    configurable: true,
    writable: true
  });
}
    
    if (typeof Object.assign != 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
      'use strict';
      if (target == null) { // TypeError if undefined or null
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) { // Skip over if undefined or null
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}
    window.microLib=Object.assign({},window.microLib,{
${list.map(({name, packageName}) => {
        return `  ['${name}']:require('${packageName}')`;
    }).join(',\n')}
});\nwindow.microLibVersion=Object.assign({},window.microLibVersion,{
${list.map(({name, version}) => {
        return `  ['${name}']:'${version}'`;
    }).join(',\n')}
});\n
var sdk = window.microLib['@knx-micro/sdk'].default;
sdk.history.listen(function(){
  window.scrollTo(0,0);
});
sdk.loadModule();`;
};

const build = (config) => {
    const compiler = webpack(config);
    return new Promise((resolve, reject) => {
        compiler.run((err, stats) => {
            let messages;
            if (err) {
                if (!err.message) {
                    return reject(err);
                }
                messages = formatWebpackMessages({
                    errors: [err.message],
                    warnings: []
                });
            } else {
                messages = formatWebpackMessages(
                    stats.toJson({all: false, warnings: true, errors: true})
                );
            }
            if (messages.errors.length) {
                // Only keep the first error. Others are often indicative
                // of the same problem, but confuse the reader with noise.
                if (messages.errors.length > 1) {
                    messages.errors.length = 1;
                }
                return reject(new Error(messages.errors.join('\n\n')));
            }
            messages.warnings.length && console.log(messages.warnings.join('\n\n'));
            return resolve();
        });
    });
};

module.exports = async (dependencies, isProduction = true) => {
    //创建临时文件夹
    const {tmpdir, cleanup} = await getTemporaryDirectory();
    //执行npm i 安装依赖包
    console.log('Installing packages. This might take a couple of minutes.');
    await new Promise((resolve, reject) => {
        const child = spawn('npm', ['install', 'babel-runtime', ...dependencies, 'es6-promise', 'react-app-polyfill', '--loglevel', 'error'], {
            stdio: 'inherit',
            cwd: tmpdir
        });
        child.on('close', (code) => {
            if (code !== 0) {
                reject({
                    command: `npm install error`
                });
                return;
            }
            resolve();
        });
    });
    //获取安装后包实际版本
    const packageInfo = await Promise.all(dependencies.map(async (name) => {
        const packageName = name.replace(/@([0-9]+\.[0-9]+\.[0-9]+.*)$/, ''),
            packageJSON = await loadJsonFile(path.resolve(tmpdir, 'node_modules', packageName, 'package.json'));
        return {name: name, packageName, version: packageJSON.version};
    }));

    //生成入口文件
    const entryPath = path.resolve(tmpdir, './src/index.js'),
        distPath = path.resolve(tmpdir, 'dist');
    await fs.ensureFile(entryPath);
    await fs.writeFile(entryPath, createEntry(packageInfo));
    //执行打包编译
    await build({
        mode: isProduction ? 'production' : 'development',
        entry: {
            'index': entryPath
        },
        devtool: 'source-map',
        output: {
            path: distPath,
            filename: '[name].js',
            library: 'master-client',
            libraryTarget: 'umd'
        },
        optimization: {
            runtimeChunk: true,
            splitChunks: {
                chunks: "all",
                cacheGroups: {
                    react: {
                        test: /(react|react-dom|react-router-dom)/,
                        name: 'react-lib'
                    },
                    util: {
                        test: /(lodash|axios)/,
                        name: 'util-lib'
                    }
                }
            }
        }
    });
    await fs.copy(distPath, path.resolve(process.cwd(), isProduction ? './umd' : './umd-dev'));
    //清理临时文件夹
    cleanup();
};